#include "StdAfx.h"
#include "MapiSms.h"
#include "resourceppc.h"

const int CMapiSms::MAX_MESSAGE = 1024;

CMapiSms::CMapiSms(void)
    : m_Count(0)
    , m_pAdrList(NULL)
{
}

CMapiSms::~CMapiSms(void)
{
    ClearRecepients();
}

HRESULT CMapiSms::SetRecepientCount(UINT Count)
{
    ClearRecepients();

    if (Count <= 0)
        return E_INVALIDARG;

    HRESULT hr = MAPIAllocateBuffer(
        CbNewADRLIST(Count),
        (LPVOID *)&m_pAdrList);
    if (FAILED(hr))
        return hr;

    m_Count = Count;
    return S_OK;
}

HRESULT CMapiSms::AddRecepient(CString &Name, CString &Number)
{
    if (m_Count == 0)
        return E_UNEXPECTED;    // SetRecepientCount not called

    if (m_pAdrList == NULL)
        return E_POINTER;   // Not allocated

    if (m_pAdrList->cEntries >= m_Count)
        return E_INVALIDARG;    // More than count

    UINT Length = Name.GetLength();
    UINT BuffSize =  (Length + 1) * sizeof(WCHAR);
    LPWSTR pName = NULL;
    HRESULT hr = MAPIAllocateBuffer(BuffSize, (LPVOID *)&pName);
    if (FAILED(hr))
        return hr;

    ZeroMemory(pName, BuffSize);
    CString::CopyChars(pName, Length + 1, Name, Length);

    Length = Number.GetLength();
    BuffSize = (Length + 1) * sizeof(WCHAR);
    LPWSTR pNumber = NULL;
    hr = MAPIAllocateBuffer(BuffSize, (LPVOID *)&pNumber);
    if (FAILED(hr))
    {
        MAPIFreeBuffer(pName);
        return hr;
    }

    ZeroMemory(pNumber, BuffSize);
    CString::CopyChars(pNumber, Length + 1, Number, Length);

    const UINT PROP_COUNT = 4;
    SPropValue *pRecepient = NULL;
    hr = MAPIAllocateBuffer(
        PROP_COUNT * sizeof(SPropValue),
        (LPVOID *)&pRecepient);
    if (FAILED(hr))
    {
        MAPIFreeBuffer(pName);
        MAPIFreeBuffer(pNumber);
        return hr;
    }

    pRecepient[0].ulPropTag = PR_RECIPIENT_TYPE;
    pRecepient[0].Value.l = MAPI_TO;

    pRecepient[1].ulPropTag = PR_ADDRTYPE;
    pRecepient[1].Value.lpszW = _T("SMS");

    pRecepient[2].ulPropTag = PR_DISPLAY_NAME;
    pRecepient[2].Value.lpszW = pName;

    ASSERT(3 < PROP_COUNT);
    pRecepient[3].ulPropTag = PR_EMAIL_ADDRESS;
    pRecepient[3].Value.lpszW = pNumber;

    m_pAdrList->aEntries[m_pAdrList->cEntries].cValues = PROP_COUNT;
    m_pAdrList->aEntries[m_pAdrList->cEntries].rgPropVals = pRecepient;
    m_pAdrList->cEntries++;

    return S_OK;
}

void CMapiSms::ClearRecepients(void)
{
    if (m_pAdrList != NULL)
    {
        for (UINT i = 0; i < m_Count; i++)
        {
            SPropValue *pRecepient = m_pAdrList->aEntries[i].rgPropVals;
            if (pRecepient != NULL)
            {
                MAPIFreeBuffer(pRecepient[2].Value.lpszW);  // Name
                MAPIFreeBuffer(pRecepient[3].Value.lpszW);  // Number
            }

            MAPIFreeBuffer(pRecepient);
        }
    }

    FreeProws((LPSRowSet)m_pAdrList);
    m_Count = 0;
}

/////////////////////////////////////////////////////////////////////////////////////////////
// This function is used to get the msgstore named SMS from msgstores on the
// device.
// I could have used just raw pointers but it is much easier and safer to just 
// use smart pointers.
HRESULT GetSMSMsgStore(IMAPISession *pSession, IMsgStore **ppMsgStore)
{
    // first we get the msgstores table from the session
    IMAPITable *pTable;
    HRESULT hr = pSession->GetMsgStoresTable(MAPI_UNICODE, &pTable);
    if (FAILED(hr))
    {
        ATLTRACE(_T("IDS_SMS_FAIL_MSGSTORE_TABLES\n"));
        return FALSE;
    }

    // next we loop over the message stores opening each msgstore and
    // getting its name to see if the name matches SMS.
    // If it does then we break out of the loop
    while (TRUE)
    {
        SRowSet* pRowSet = NULL;
        hr = pTable->QueryRows(1, 0, &pRowSet);

        // If we failed to query the
        // rows then we need to break
        if (FAILED(hr))
        {
            ATLTRACE(_T("IDS_SMS_FAILEDTABLE\n"));
            break;
        }

        // if we got no rows back then just exit the loop
        //remembering to set an error
        if (pRowSet->cRows == 1)
        {
            ASSERT(pRowSet->aRow[0].lpProps->ulPropTag == PR_ENTRYID);
            SBinary& blob = pRowSet->aRow[0].lpProps->Value.bin;
            hr = pSession->OpenMsgStore(NULL, blob.cb, (LPENTRYID)blob.lpb, NULL, 0, ppMsgStore);
            if (FAILED(hr))
                ATLTRACE(_T("IDS_SMS_FAILED_OPENMSGSTORE\n"));

        }
        else
        {
            ATLTRACE(_T("IDS_SMS_MSGSTORENOTFOUND\n"));
            hr = HRESULT_FROM_WIN32(ERROR_NOT_FOUND);
        }

        // now remember to free the row set
        FreeProws(pRowSet);
        if (FAILED(hr))
        {
            break;
        }

        // now get the display name property from the
        // message store to compare it against the name
        // 'SMS'
        SPropTagArray props;
        props.cValues = 1;
        props.aulPropTag[0] = PR_DISPLAY_NAME;

        ULONG cValues;
        SPropValue* pProps = NULL;
        hr = (*ppMsgStore)->GetProps(&props, MAPI_UNICODE, &cValues, &pProps);
        if (FAILED(hr) || cValues != 1)
        {
            ATLTRACE(_T("IDS_SMS_FAILED_GETNAME\n"));
            break;
        }

        // if the name matches SMS then break and as
        // hr == S_OK the current MsgStore smart pointer
        // will correctly be set.
        if (_tcsicmp(pProps[0].Value.lpszW, _T("SMS")) == 0)
        {
            break;
        }
    }

    // if we failed for some reason then we clear out
    // the msgstore smartpointer and return the error.
    if (FAILED(hr))
    {
        (*ppMsgStore)->Release();
    }

    return hr;
}


/////////////////////////////////////////////////////////////////////////////////////////////
// This function is used to get the folder named drafts from the msgstore on the
// device.
// I could have used just raw pointers but it is much easier and safer to just 
// use smart pointers.
HRESULT GetSMSFolder(IMsgStore *pMsgStore, IMAPIFolder **ppFolder)
{
    // Now get the Drafts folder.
    SPropTagArray propDefaultFolder;
    propDefaultFolder.cValues = 1;
    propDefaultFolder.aulPropTag[0] = PR_CE_IPM_DRAFTS_ENTRYID;

    ULONG cValues;
    LPSPropValue pPropVals;
    HRESULT hr = pMsgStore->GetProps (&propDefaultFolder, MAPI_UNICODE, &cValues, &pPropVals);
    if (FAILED(hr))
    {
        ATLTRACE(_T("IDS_SMS_FOLDERNOTFOUND\n"));
        return hr;
    }

    SBinary& eidDrafts = pPropVals->Value.bin;

    hr = pMsgStore->OpenEntry(eidDrafts.cb, (LPENTRYID)eidDrafts.lpb, NULL, MAPI_MODIFY, NULL, (LPUNKNOWN*)ppFolder);
    if (FAILED(hr))
    {
        ATLTRACE(_T("IDS_SMS_FOLDERNOTOPENED\n"));
    }

    return hr;
}

HRESULT
CMapiSms::MapiSend(
    IMAPISession *pSession,
    CString &Message,
    bool DeliveryNotification)
{
    // now get the SMS message store
    IMsgStore *pMsgStore;
    HRESULT hr = GetSMSMsgStore(pSession, &pMsgStore);
    if (FAILED(hr))
    {
        return hr;
    }

    IMAPIFolder *pFolder;
    hr = GetSMSFolder(pMsgStore, &pFolder);
    if (FAILED(hr))
    {
        pMsgStore->Release();
        return hr;
    }

    IMessage *pMessage;
    hr = pFolder->CreateMessage(NULL, 0 , &pMessage);
    if (FAILED(hr))
    {
        ATLTRACE(_T("IDS_SMS_FAIL_CREATEMESSAGE\n"));
        pMsgStore->Release();
        pFolder->Release();
        return hr;
    }

    // finally modify the recipients of the message
    hr = pMessage->ModifyRecipients(MODRECIP_ADD, m_pAdrList); 
    if (FAILED(hr))
    {
        ATLTRACE(_T("IDS_SMS_FAILED_ADDRECIPIENTS\n"));
        pMsgStore->Release();
        pFolder->Release();
        return hr;
    }

    // now we set the additional properties for the 
    // message
    SPropValue props[7];

    //note how we zero out the contents of the
    // structure as MAPI is sensative to the
    // contents of other fields we do not use.
    ZeroMemory(&props, sizeof(props));

    // first set the subject of the message
    // as the sms we are going to send
    props[0].ulPropTag = PR_SUBJECT;
    props[0].Value.lpszW = (LPWSTR)((LPCWSTR)Message);

    // finally and most importantly tell mapi
    // this is a sms message in need of delivery
    props[1].ulPropTag = PR_MSG_STATUS;
    props[1].Value.ul = MSGSTATUS_RECTYPE_SMS;

    props[2].ulPropTag = PR_MESSAGE_FLAGS;
    props[2].Value.ul = MSGFLAG_UNSENT;

    props[3].ulPropTag = PR_MESSAGE_CLASS;
    props[3].Value.lpszW = TEXT("IPM.SMStext");

    props[4].ulPropTag = PR_MESSAGE_SIZE;
    props[4].Value.ul = MAX_MESSAGE;   // Cannot be more than this

    props[5].ulPropTag = PR_ORIGINATOR_DELIVERY_REPORT_REQUESTED;
    props[5].Value.b = DeliveryNotification;

    props[6].ulPropTag = PR_PROOF_OF_DELIVERY_REQUESTED;
    props[6].Value.b = DeliveryNotification;

    hr = pMessage->SetProps(sizeof(props) / sizeof(props[0]), (LPSPropValue)&props, NULL);
    if (FAILED(hr))
    {
        ATLTRACE(_T("IDS_SMS_FAIL_SETPROPS\n"));
        pMsgStore->Release();
        pFolder->Release();
        return hr;
    }

    // having set all the required fields we can now
    // pass the message over to the msgstore transport
    // to be delivered.
    hr = pMessage->SubmitMessage(0);
    if (FAILED(hr))
    {
        ATLTRACE(_T("IDS_SMS_FAIL_SUBMITMSG\n"));
        pMsgStore->Release();
        pFolder->Release();
        return hr;
    }

    pMessage->Release();
    pMsgStore->Release();
    pFolder->Release();

    return S_OK;
}

HRESULT CMapiSms::Send(CString &Message, bool DeliveryNotification)
{
    if (Message.GetLength() > MAX_MESSAGE)
        return E_INVALIDARG;

    HRESULT hr = MAPIInitialize(NULL);
    if (FAILED(hr))
    {
        ATLTRACE(_T("IDS_SMS_FAIL_MAPIINIT\n"));
        return hr;
    }

    IMAPISession *pSession = NULL;
    BOOL bRet = FALSE;

    hr = MAPILogonEx(0 ,NULL, NULL, 0, &pSession);
    if (FAILED(hr))
    {
        ATLTRACE(_T("IDS_SMS_FAIL_MAPILOGON\n"));
    }
    else
    {
        hr = MapiSend(pSession, Message, DeliveryNotification);

        pSession->Logoff(0, 0, 0);
        pSession->Release();
    }

    MAPIUninitialize();

    return hr;
}
